package com.biscuit.page.basicservice.service;

import com.alibaba.fastjson.JSON;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.InvalidClaimException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.biscuit.common.base.BaseResponse;
import com.biscuit.common.base.BaseResponseVoid;
import com.biscuit.page.basicservice.anno.LoginLog;
import com.biscuit.page.basicservice.api.model.request.authorization.AuthorizationAReq;
import com.biscuit.page.basicservice.api.model.request.authorization.AuthorizationGetVerifyCodeAReq;
import com.biscuit.page.basicservice.api.model.request.common.CommonGetVerifyCodeAReq;
import com.biscuit.page.basicservice.api.model.response.authorization.AuthorizationAResp;
import com.biscuit.page.basicservice.api.model.response.user.UserInfoAResp;
import com.biscuit.page.basicservice.bean.RedisCache;
import com.biscuit.page.basicservice.conf.handler.UserContextHandler;
import com.biscuit.page.basicservice.convert.UserDetailVOConvert;
import com.biscuit.page.basicservice.design.login.factory.LoginMoreTypeFactory;
import com.biscuit.page.basicservice.model.constant.redis.RedisAuthorizationConstant;
import com.biscuit.page.basicservice.model.entities.mysql.SysUser;
import com.biscuit.page.basicservice.model.entities.mysql.SysUserDetail;
import com.biscuit.page.basicservice.model.enums.BaseResponseEnum;
import com.biscuit.page.basicservice.persistence.dao.UserDao;
import com.biscuit.page.basicservice.persistence.dao.UserDetailDao;
import com.biscuit.page.basicservice.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @author mazihao
 * @createTime 2024/3/30 11:08
 */
@Slf4j
@Service
public class AuthorizationService {

    @Resource
    private RedisCache redisCache;

    @Resource
    private UserDao userDao;

    @Resource
    private UserDetailDao userDetailDao;

    @Resource
    private CommonService commonService;


    @Resource
    private LoginMoreTypeFactory loginMoreTypeFactory;

    @Resource
    private UserDetailVOConvert userDetailVoConvert;

    /**
     * 多类型登录入口
     */
    @LoginLog
    public BaseResponse<AuthorizationAResp> login(AuthorizationAReq req) {
        AuthorizationAResp data = new AuthorizationAResp();

        // 参数校验
        BaseExceptionUtil.throwException(StringUtils.isNotBlank(req.getAccount()), "请输入账号");

        // 判断登录标识为手机号还是邮箱，或者非法类型
        int accountType = this.verifyLoginAccountType(req.getAccount());
        BaseExceptionUtil.throwException(accountType != -1, "账号类型错误");
        req.setAccountType(accountType);

        // 根据枚举类型选择登录方式，并获取用户登录信息
        SysUser sysUser = this.loginMoreTypeFactory.create(req.getLoginType()).login(req);

        // 校验账户状态
        BaseExceptionUtil.throwException(sysUser.getStatus() == 1, "账号状态异常");

        // 根据用户id查询用户详情信息
        SysUserDetail sysUserDetail = this.userDetailDao.getByUserId(sysUser.getId());

        // 组装用户信息缓存key
        String cacheKey = RedisAuthorizationConstant.AUTHORIZATION_USER_PREFIX + sysUser.getId();

        // 组装用户详情信息缓存key
        String cacheKeySetting = RedisAuthorizationConstant.AUTHORIZATION_USER_DETAIL_PREFIX + sysUser.getId();

        // 缓存用户信息
        this.redisCache.setObject(cacheKey, sysUser, 2, TimeUnit.DAYS);

        // 缓存用户配置信息
        this.redisCache.setObject(cacheKeySetting, sysUserDetail, 2, TimeUnit.DAYS);

        // 生成 Jwt 标识，有效期为两天
        String token = JwtUtil.getToken(sysUser.getId().toString(), 60 * 60 * 24 * 2);

        // 异步修改登录时间
        CompletableFuture.runAsync(() -> this.userDao.updateLastLoginTime(sysUser.getId(), LocalDateTime.now()));

        // 返回用户信息
        AuthorizationAResp.AuthorizationInfo authorizationInfo = new AuthorizationAResp.AuthorizationInfo();
        BeanUtils.copyProperties(sysUser, authorizationInfo);
        authorizationInfo.setRegisterTime(sysUser.getCreateTime());
        authorizationInfo.setUserDetailVO(this.userDetailVoConvert.req2Vo(sysUserDetail));

        data.setInfo(authorizationInfo);
        data.setAuthorization(token);

        return BaseResponseUtil.success(data);
    }

    /**
     * 获取验证码
     */
    public BaseResponse<BaseResponseVoid> getVerifyCode(AuthorizationGetVerifyCodeAReq req) {
        // 参数校验
        BaseExceptionUtil.throwException(StringUtils.isNotBlank(req.getLoginText()), "手机号或邮箱为空");
        BaseExceptionUtil.throwException(NumberUtil.numberCheck(req.getType()), "验证码类型错误");

        // 根据类型发送验证码
        CommonGetVerifyCodeAReq innerReq = new CommonGetVerifyCodeAReq();
        innerReq.setTarget(req.getLoginText());
        innerReq.setWorkType(req.getType());
        this.commonService.getVerifyCode(innerReq);

        return BaseResponseUtil.success();
    }

    /**
     * 退出登录
     */
    public BaseResponse<BaseResponseVoid> logout() {
        try {
            // 获取用户 Token
            String authorization = UserContextHandler.getAuthorization();

            // 从 Token 中解析出用户id
            String userId = JwtUtil.getUserKey(authorization);

            // 异步清除缓存中的用户信息
            BaseThreadUtil.async(() -> {
                this.redisCache.delete(
                        RedisAuthorizationConstant.AUTHORIZATION_USER_PREFIX + userId,
                        RedisAuthorizationConstant.AUTHORIZATION_USER_DETAIL_PREFIX + userId
                );
            });
        } catch (Exception e) {
            log.error("AccountService logout error -> {}", JSON.toJSONString(e));
        }
        return BaseResponseUtil.success();
    }

    public UserInfoAResp getUserInfoByCurrentLogin() {
        // 通过请求的线程变量数据判断是否登录
        BaseExceptionUtil.throwException(UserContextHandler.isLogin(), "用户未登录");

        // 用户信息赋值
        UserInfoAResp userInfoResp = new UserInfoAResp();
        BeanUtils.copyProperties(UserContextHandler.getContext().getUser(), userInfoResp);
        return userInfoResp;
    }

    // 根据 token 获取用户信息，无报错信息
    public SysUser getUserInfoByTokenNoError(String authorization) {
        try {
            // 校验 token 是否合法
            JwtUtil.verify(authorization);

            // 从 token 中解析出用户的 userId
            String userId = JwtUtil.getUserKey(authorization);

            // 校验解析内容是否为空
            BaseExceptionUtil.throwException(StringUtils.isNotBlank(userId), "解析用户 Token 失败");

            // 根据用户信息从缓存中读取用户信息
            SysUser sysUser = this.redisCache.get2Json(RedisAuthorizationConstant.AUTHORIZATION_USER_PREFIX + userId, SysUser.class);

            // 校验缓存中的用户信息是否过期
            BaseExceptionUtil.throwException(Objects.nonNull(sysUser), "登录凭证已过期，请重新登录", BaseResponseEnum.ERROR_UNAUTHORIZED);

            // 返回用户信息
            return sysUser;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 获取登录账号类型
     *
     * @param account 登录账号
     * @return 1 邮箱，2 手机号，-1非法
     */
    private int verifyLoginAccountType(String account) {
        if (RegexUtil.validateEmail(account)) {
            return 1;
        }
        if (RegexUtil.validateMobile(account)) {
            return 2;
        }
        return -1;
    }

    /**
     * 校验 Token 是否合法
     */
    public String checkToken(String authorization) {
        String msg = "用户未登录";
        if (StringUtils.isBlank(authorization)) {
            return JSON.toJSONString(BaseResponseUtil.fail(BaseResponseEnum.ERROR_UNAUTHORIZED, "token为空"));
        }
        try {
            JwtUtil.verify(authorization);
        } catch (SignatureVerificationException | AlgorithmMismatchException | InvalidClaimException e) {
            msg = "访问未授权";
        } catch (TokenExpiredException e) {
            msg = "登录已过期";
        }
        if (StringUtils.isBlank(msg)) {
            String userKey = JwtUtil.getUserKey(authorization);
            String cacheKey = RedisAuthorizationConstant.AUTHORIZATION_USER_PREFIX + userKey;
            boolean exists = this.redisCache.isKeyExists(cacheKey);
            if (exists) {
                return null;
            }
            msg = "登录已过期";
        }
        return JSON.toJSONString(BaseResponseUtil.fail(BaseResponseEnum.ERROR_UNAUTHORIZED, msg));
    }

}
